// This example draws an animated dial with a rotating needle.

// The dial is a jpeg image, the needle is created using a rotated
// Sprite. The example operates by reading blocks of pixels from the
// TFT, thus the TFT setup must support reading from the TFT CGRAM.

// The sketch operates by creating a copy of the screen block where
// the needle will be drawn, the needle is then drawn on the screen.
// When the needle moves, the original copy of the screen area is
// pushed to the screen to over-write the needle graphic. A copy
// of the screen where the new position will be drawn is then made
// before drawing the needle in the new position. This technique
// allows the needle to move over other screen graphics.

// The sketch calculates the size of the buffer memory required and
// reserves the memory for the TFT block copy.

// Created by Bodmer 17/3/20 as an example to the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI

#define WIFI_SSID      ""
#define WIFI_PASSWORD  ""


#define CLOCK_FG   0x00E0
#define CLOCK_BG   TFT_WHITE
#define SECCOND_FG TFT_RED

#define DIAL_CENTRE_X 120.0
#define DIAL_CENTRE_Y 170.0

// Calculate 1 second increment angles. Hours and minute hand angles
// change every second so we see smooth sub-pixel movement
#define SECOND_ANGLE 360.0 / 60.0
#define MINUTE_ANGLE SECOND_ANGLE / 60.0
#define HOUR_ANGLE   MINUTE_ANGLE / 12.0

#define CLOCK_R       45.0f // Clock face radius (float type)
#define H_HAND_LENGTH CLOCK_R/1.8f
#define M_HAND_LENGTH CLOCK_R/1.2f
#define S_HAND_LENGTH CLOCK_R
// Sprite width and height
#define FACE_W S_HAND_LENGTH * 2 - 1
#define FACE_H S_HAND_LENGTH * 2 - 1

#define TAIL_WIDTH  55
#define TAIL_LENGTH 61
#define TAIL_FRAME  2      //尾巴1秒钟动几帧


#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle
TFT_eSprite spr    = TFT_eSprite(&tft); // Sprite for needles
TFT_eSprite tail    = TFT_eSprite(&tft);

// Jpeg image array attached to this sketch
#include "owl.h"

// Include the jpeg decoder library
#include <TJpg_Decoder.h>

uint16_t* tft_bufferH;
uint16_t* tft_bufferM;
uint16_t* tft_bufferS;
uint16_t* tft_bufferT;
bool      buffer_loadedH = false;
bool      buffer_loadedM = false;
bool      buffer_loadedS = false;
bool      buffer_loadedTail = false;

// Time h:m:s
uint8_t h = 0, m = 0, s = 0;
float time_secs = h * 3600 + m * 60 + s;

// Load header after time_secs global variable has been created so it is in scope
#include "NTP_Time.h" // Attached to this sketch, see that tab for library needs



// =======================================================================================
// This function will be called during decoding of the jpeg file
// =======================================================================================
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
{
  // Stop further decoding as image is running off bottom of screen
  if ( y >= tft.height() ) return 0;

  // This function will clip the image block rendering automatically at the TFT boundaries
  tft.pushImage(x, y, w, h, bitmap);

  // Return 1 to decode next block
  return 1;
}

// =======================================================================================
// Setup
// =======================================================================================
void setup()   {
  Serial.begin(115200); // Debug only

  // The byte order can be swapped (set true for TFT_eSPI)
  TJpgDec.setSwapBytes(true);

  // The jpeg decoder must be given the exact name of the rendering function above
  TJpgDec.setCallback(tft_output);

  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);

  // Draw the dial
  TJpgDec.drawJpg(0, 0, dial, sizeof(dial));

  //表盘初始化
  //needle.setColorDepth(16);
  needle.createSprite(FACE_W, FACE_H);  // create the needle Sprite

  //  tft_bufferH =  (uint16_t*) malloc( (H_HAND_LENGTH + 10) *  (H_HAND_LENGTH + 10) * 2);   //时针
  //  Serial.println((H_HAND_LENGTH + 10) *  (H_HAND_LENGTH + 10) * 2);  //2450.00
  //  tft_bufferM =  (uint16_t*) malloc( (M_HAND_LENGTH + 10) *  (M_HAND_LENGTH + 10) * 2);   //分针
  //  Serial.println((M_HAND_LENGTH + 10) *  (M_HAND_LENGTH + 10) * 2);   //4512.50
  //  tft_bufferS =  (uint16_t*) malloc( (S_HAND_LENGTH + 10) *  (S_HAND_LENGTH + 10) * 2);   //秒针
  //  Serial.println((S_HAND_LENGTH + 10) *  (S_HAND_LENGTH + 10) * 2); //6050.00

  //尾巴初始化
  tail.setColorDepth(16);
  tail.createSprite(TAIL_WIDTH, TAIL_LENGTH);
  tail.setSwapBytes(true);
  tail.pushImage(0, 0, TAIL_WIDTH, TAIL_LENGTH, tailbmp);
  tft.setPivot(DIAL_CENTRE_X, DIAL_CENTRE_Y);
  uint16_t piv_x = TAIL_WIDTH / 2; // pivot x in Sprite (middle)
  uint16_t piv_y = -85;    // pivot y in Sprite
  tail.setPivot(piv_x, piv_y);     // Set pivot point in this Sprite

  //  // Bounding box parameters to be populated
  //  int16_t min_x;
  //  int16_t min_y;
  //  int16_t max_x;
  //  int16_t max_y;
  //
  //  // Work out the worst case area that must be grabbed from the TFT,
  //  // this is at a 7 degree rotation
  //  tail.getRotatedBounds(7, &min_x, &min_y, &max_x, &max_y);
  //Serial.println(((max_x - min_x) + 2) * ((max_y - min_y) + 2) * 2);  //=9782


  //tft_bufferT =  (uint16_t*) malloc( ((max_x - min_x) + 2) * ((max_y - min_y) + 2) * 2 ); //尾

  //tft.initDMA();  //启用后响应时间没变化
}

// =======================================================================================
// Loop
// =======================================================================================
void loop() {
  // Update time periodically
  static uint32_t previousMillis = 0;
  uint32_t currentMillis = millis();
  static uint8_t i = 0;            //尾巴帧数计数

  if ( currentMillis - previousMillis >= 1000) {

    previousMillis = currentMillis;

    // Increment time by 100 milliseconds
    time_secs += 1.000;

    // Midnight roll-over
    if (time_secs >= (60 * 60 * 24)) time_secs = 0;

    // Request time from NTP server and synchronise the local clock
    // (clock may pause since this may take >100ms)
    syncTime();

    // All graphics are drawn in sprite to stop flicker
    plotNeedle(time_secs);
    //Serial.println(millis() - previousMillis);
    i = 0;
  }

  if (currentMillis - previousMillis > i * 1000 / TAIL_FRAME )
  {
    swingingTail(i);
    //Serial.println(i);
    i ++;
  }
}


// =======================================================================================
// Move the needle to a new position 整个函数平均耗时40ms
// =======================================================================================
void plotNeedle(float t)
{
  float h_angle = t * HOUR_ANGLE;
  float m_angle = t * MINUTE_ANGLE;
  float s_angle = t * SECOND_ANGLE;
  static float xHp = 0.0, yHp = 0.0, xMp = 0.0, yMp = 0.0, xSp = 0.0, ySp = 0.0; // Use float pixel position for smooth AA motion
  static float xHc = 0.0, yHc = 0.0, xMc = 0.0, yMc = 0.0, xSc = 0.0, ySc = 0.0; //补块左上角坐标

  //uint32_t temp = millis();

  //补块秒针
  if (buffer_loadedS) {
    tft.pushRect(xSc + DIAL_CENTRE_X - CLOCK_R - 4, ySc + DIAL_CENTRE_Y - CLOCK_R - 4, abs(xSp - CLOCK_R) + 8, abs(ySp - CLOCK_R) + 8, tft_bufferS);
    //    tft.startWrite();
    //    tft.pushImageDMA(xSc + DIAL_CENTRE_X - CLOCK_R - 4, ySc + DIAL_CENTRE_Y - CLOCK_R - 4, abs(xSp - CLOCK_R) + 8, abs(ySp - CLOCK_R) + 8, tft_bufferS);
    //    tft.endWrite();
    free(tft_bufferS);
  }

  getCoord(CLOCK_R, CLOCK_R, &xSp, &ySp, S_HAND_LENGTH, s_angle);
  if (xSp <= CLOCK_R)  xSc = xSp;
  else xSc = CLOCK_R;
  if (ySp <= CLOCK_R)  ySc = ySp;
  else ySc = CLOCK_R;
  tft_bufferS =  (uint16_t*) malloc( 6050 );//setup里计算
  tft.readRect(xSc + DIAL_CENTRE_X - CLOCK_R - 4, ySc + DIAL_CENTRE_Y - CLOCK_R - 4, abs(xSp - CLOCK_R) + 8, abs(ySp - CLOCK_R) + 8, tft_bufferS);
  buffer_loadedS = true;


  //补块分针
  if (buffer_loadedM) {
    tft.pushRect(xMc + DIAL_CENTRE_X - CLOCK_R - 8, yMc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xMp - CLOCK_R) + 16, abs(yMp - CLOCK_R) + 16, tft_bufferM);
    //    tft.startWrite();
    //    tft.pushImageDMA(xMc + DIAL_CENTRE_X - CLOCK_R - 8, yMc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xMp - CLOCK_R) + 16, abs(yMp - CLOCK_R) + 16, tft_bufferM);
    //    tft.endWrite();
    free(tft_bufferM);
  }

  getCoord(CLOCK_R, CLOCK_R, &xMp, &yMp, M_HAND_LENGTH, m_angle);
  if (xMp <= CLOCK_R)  xMc = xMp;
  else xMc = CLOCK_R;
  if (yMp <= CLOCK_R)  yMc = yMp;
  else yMc = CLOCK_R;
  tft_bufferM =  (uint16_t*) malloc( 4512 );//setup里计算
  tft.readRect(xMc + DIAL_CENTRE_X - CLOCK_R - 8, yMc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xMp - CLOCK_R) + 16, abs(yMp - CLOCK_R) + 16, tft_bufferM);
  buffer_loadedM = true;


  //补块时针
  if (buffer_loadedH) {
    tft.pushRect(xHc + DIAL_CENTRE_X - CLOCK_R - 8, yHc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xHp - CLOCK_R) + 16, abs(yHp - CLOCK_R) + 16, tft_bufferH);
    //    tft.startWrite();
    //    tft.pushImageDMA(xHc + DIAL_CENTRE_X - CLOCK_R - 8, yHc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xHp - CLOCK_R) + 16, abs(yHp - CLOCK_R) + 16, tft_bufferH);
    //    tft.endWrite();
    free(tft_bufferH);
  }

  getCoord(CLOCK_R, CLOCK_R, &xHp, &yHp, H_HAND_LENGTH, h_angle);
  if (xHp <= CLOCK_R)  xHc = xHp;
  else xHc = CLOCK_R;
  if (yHp <= CLOCK_R)  yHc = yHp;
  else yHc = CLOCK_R;
  tft_bufferH =  (uint16_t*) malloc( 2450 );//setup里计算
  tft.readRect(xHc + DIAL_CENTRE_X - CLOCK_R - 8, yHc + DIAL_CENTRE_Y - CLOCK_R - 8, abs(xHp - CLOCK_R) + 16, abs(yHp - CLOCK_R) + 16, tft_bufferH);
  buffer_loadedH = true;

  //   Serial.print("补块：");Serial.print(millis()-temp);Serial.print("  重绘表盘：");

  //清屏
  needle.fillSprite(TFT_TRANSPARENT); // Fill with TRANSPARENT
  // Draw hour hand
  getCoord(CLOCK_R, CLOCK_R, &xHp, &yHp, H_HAND_LENGTH, h_angle);
  needle.drawWideLine(CLOCK_R, CLOCK_R, xHp, yHp, 6.0f, CLOCK_FG, TFT_TRANSPARENT);
  needle.drawWideLine(CLOCK_R, CLOCK_R, xHp, yHp, 2.0f, CLOCK_BG, TFT_TRANSPARENT);


  // Draw minute hand
  getCoord(CLOCK_R, CLOCK_R, &xMp, &yMp, M_HAND_LENGTH, m_angle);
  needle.drawWideLine(CLOCK_R, CLOCK_R, xMp, yMp, 6.0f, CLOCK_FG, TFT_TRANSPARENT);
  needle.drawWideLine(CLOCK_R, CLOCK_R, xMp, yMp, 2.0f, CLOCK_BG, TFT_TRANSPARENT);

  // Draw the central pivot circle
  needle.fillSmoothCircle(CLOCK_R, CLOCK_R, 4, CLOCK_FG);

  // Draw second hand
  getCoord(CLOCK_R, CLOCK_R, &xSp, &ySp, S_HAND_LENGTH, s_angle);
  //needle.drawWideLine(CLOCK_R, CLOCK_R, xSp, ySp, 2.0f, SECCOND_FG);
  needle.drawWedgeLine(CLOCK_R, CLOCK_R, xSp, ySp, 2.0f, 1.0, SECCOND_FG, TFT_TRANSPARENT);
  needle.pushSprite(DIAL_CENTRE_X - CLOCK_R , DIAL_CENTRE_Y - CLOCK_R , TFT_TRANSPARENT);

  //  Serial.println(millis()-temp);
}


//
//
//// =========================================================================
//// 判断浮点数是否为整数
//// =========================================================================
//bool is_float_integer(float num) {
//  uint32_t int_num = (uint32_t)num;
//  float diff = fabs(num - int_num);
//  return diff < 1e-6;  // 阈值可以根据需要调整
//}
//


// =========================================================================
// Get coordinates of end of a line, pivot at x,y, length r, angle a
// =========================================================================
// Coordinates are returned to caller via the xp and yp pointers
#define DEG2RAD 0.0174532925
void getCoord(int16_t x, int16_t y, float *xp, float *yp, int16_t r, float a)
{
  float sx1 = cos( (a - 90) * DEG2RAD);
  float sy1 = sin( (a - 90) * DEG2RAD);
  *xp =  sx1 * r + x;
  *yp =  sy1 * r + y;
}


// =======================================================================================
// Move the tail fin
// =======================================================================================
void swingingTail(uint8_t count)
{
  // Bounding box parameters
  static int16_t min_x;
  static int16_t min_y;
  static int16_t max_x;
  static int16_t max_y;
  static int8_t angleT;
  static int8_t angleMAX = 7, flag = 1;


  if (buffer_loadedTail) {
    // Paste back the original needle free image area
    tft.pushRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_bufferT);
//    tft.startWrite();
//    tft.pushImageDMA(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_bufferT);
//    tft.endWrite();
    free (tft_bufferT);
  }
  if (flag == -1)
  {
    count = TAIL_FRAME - count - 1;
    if (count == TAIL_FRAME -  1)
      flag = 1;
  }
  angleT = angleMAX * cos( 360 * (count + 1) / TAIL_FRAME * DEG2RAD);   //Serial.println(angleT);
  if (count >= TAIL_FRAME - 1) {
    flag = -1;
  }

  tft_bufferT =  (uint16_t*) malloc( 9782 );  //setup里注释计算好了

  if ( tail.getRotatedBounds(angleT, &min_x, &min_y, &max_x, &max_y) )
  {
    // Grab a copy of the area before needle is drawn
    tft.readRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_bufferT);
    buffer_loadedTail = true;
  }

  // Draw the tail in the new position
  tail.pushRotated(angleT, TFT_BLACK);

}



// =======================================================================================
